﻿/* 
 * A PART OF code in this file were distributed under Microsoft Public License (Ms-PL).
 * http://www.microsoft.com/en-us/openness/licenses.aspx#MPL
 * Thanks to https://github.com/ricmrodrigues/autosprite
 * 
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using ImgCore.Sprite.Utility;

namespace ImgCore.Sprite
{

    public class SpriteInfo
    {
        public Dictionary<int, InputFile> inputFiles;
        public string outputSpriteFilePath;
        public string outputCssFilePath;
        public int distanceBetweenImages;
        public int marginWidth;

        public SpriteInfo()
        {
            inputFiles = null;
            outputSpriteFilePath = "";
            outputCssFilePath = "";
            distanceBetweenImages = 0;
            marginWidth = 0;
        }
    }

    public class InputFile
    {
        public string file;
        public string classname;
        public Img img;

        public InputFile(string _file, Img _img)
        {
            file = _file;
            img = _img;
            classname = System.IO.Path.GetFileNameWithoutExtension(_file);
        }
    }

    public class Sprite
    {
        private SpriteInfo input;

        public Sprite(SpriteInfo _input)
        {
            input = _input;
        }


        public void Create()
        {            

            StreamWriter cssFile = File.CreateText(input.outputCssFilePath);
            Image resultSprite = null;

            cssFile.WriteLine(".sprite { background-image: url('" +
                relativeSpriteImagePath(input.outputSpriteFilePath, input.outputCssFilePath) +
                "'); background-color: transparent; background-repeat: no-repeat; }");

            resultSprite = generateAutomaticLayout(cssFile);

            cssFile.Close();
            resultSprite.Save(input.outputSpriteFilePath);
        }

        private List<Module> CreateModules()
        {
            List<Module> modules = new List<Module>();
            input.inputFiles.All(i =>
                {
                    modules.Add(new Module(i.Key, i.Value.img.Image, input.distanceBetweenImages));
                    return true;
                });
            return modules;
        }

        //CSS line
        private string CssLine(string cssClassName, Rectangle rectangle)
        {
            string line = "." + cssClassName + " { width: " + rectangle.Width.ToString() + "px; height: " + rectangle.Height.ToString() + 
                "px; background-position: " + (-1 * rectangle.X).ToString() + "px " + (-1 * rectangle.Y).ToString() + "px; }";
            return line;
        }

        //Relative sprite image file path
        private string relativeSpriteImagePath(string outputSpriteFilePath, string outputCssFilePath)
        {
            string[] splittedOutputCssFilePath = outputCssFilePath.Split('\\');
            string[] splittedOutputSpriteFilePath = outputSpriteFilePath.Split('\\');

            int breakAt = 0;
            for (int i = 0; i < splittedOutputCssFilePath.Length; i++)
                if (i < splittedOutputSpriteFilePath.Length && splittedOutputCssFilePath[i] != splittedOutputSpriteFilePath[i])
                {
                    breakAt = i;
                    break;
                }

            string relativePath = "";
            for (int i = 0; i < splittedOutputCssFilePath.Length - breakAt - 1; i++)
                relativePath += "../";
            relativePath += String.Join("/", splittedOutputSpriteFilePath, breakAt, splittedOutputSpriteFilePath.Length - breakAt);

            return relativePath;
        }

        //Automatic layout
        private Image generateAutomaticLayout(StreamWriter cssFile)
        {
            var sortedByArea = from m in CreateModules()
                               orderby m.Width * m.Height descending
                               select m;
            List<Module> moduleList = sortedByArea.ToList<Module>();
            Placement placement = Algorithm.Greedy(moduleList);

            //Creating an empty result image.
            Image resultSprite = new Bitmap(placement.Width - input.distanceBetweenImages + 2 * input.marginWidth,
                placement.Height - input.distanceBetweenImages + 2 * input.marginWidth);
            Graphics graphics = Graphics.FromImage(resultSprite);
            
            //Drawing images into the result image in the original order and writing CSS lines.
            foreach (Module m in placement.Modules)
            {
                m.Draw(graphics, input.marginWidth);
                Rectangle rectangle = new Rectangle(m.X + input.marginWidth, m.Y + input.marginWidth,
                    m.Width - input.distanceBetweenImages, m.Height - input.distanceBetweenImages);
                cssFile.WriteLine(CssLine(input.inputFiles[m.Name].classname, rectangle));
            }

            return resultSprite;
        }

    }

    public class Placement
    {
        private List<Module> modules;

        public Placement(List<Module> _modules)
        {
            modules = _modules;
        }

        /// <summary>
        /// Gets the half perimeter of the placement.
        /// </summary>
        public int Perimeter
        {
            get { return modules.Max(m => m.X + m.Width) + modules.Max(m => m.Y + m.Height); }
        }

        /// <summary>
        /// Gets the width of the palcement.
        /// </summary>
        public int Width
        {
            get { return modules.Max(m => m.X + m.Width); }
        }

        /// <summary>
        /// Gets the height of the placement.
        /// </summary>
        public int Height
        {
            get { return modules.Max(m => m.Y + m.Height); }
        }

        /// <summary>
        /// Gets the modules in the placement.
        /// </summary>
        public List<Module> Modules
        {
            get { return modules; }
        }
    }
}
